﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlServerCe;
using Kokomo.PeInspector.Reflection.Disassembly;
using System.Reflection;
using Kokomo.PeInspector.Reflection;
using Kokomo.Mirror.CliMetadata;

namespace Kokomo.PeInspector.Data
{
	class CacheBuilder : IDisposable
	{
		internal CacheBuilder(
			AssemblyIndexCache cache,
			string connectionString
			)
		{
			this._cache = cache;
			this._connectionString = connectionString;
			this._moduleTable = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);

			int? lastMemberDependencyID = cache.LastMemberDependencyID();
			this._nextMemberDependencyID = (lastMemberDependencyID.HasValue ? lastMemberDependencyID.Value + 1 : 1);

			int? lastStringDependencyID = cache.LastStringDependencyID();
			this._nextStringDependencyID = (lastStringDependencyID.HasValue ? lastStringDependencyID.Value + 1 : 1);

			this.OpenResultSet(
				"MemberDependencies",
				out this._memberConnection,
				out this._memberCommand,
				out this._memberResultSet
				);
			this._member_RowID = this._memberResultSet.GetOrdinal("RowId");
			this._member_ModuleID = this._memberResultSet.GetOrdinal("DependentModuleId");
			this._member_Token = this._memberResultSet.GetOrdinal("DependentToken");
			this._member_TargetModuleID = this._memberResultSet.GetOrdinal("DependencyModuleId");
			this._member_TargetToken = this._memberResultSet.GetOrdinal("DependencyToken");
			this._member_MemberType = this._memberResultSet.GetOrdinal("MemberType");
			this._member_Usage = this._memberResultSet.GetOrdinal("Usage");
			this._member_Offset = this._memberResultSet.GetOrdinal("Offset");

			this.OpenResultSet(
				"StringDependencies",
				out this._stringConnection,
				out this._stringCommand,
				out this._stringResultSet
				);
			this._string_RowID = this._stringResultSet.GetOrdinal("RowId");
			this._string_ModuleID = this._stringResultSet.GetOrdinal("DependentModuleId");
			this._string_Token = this._stringResultSet.GetOrdinal("DependentToken");
			this._string_Text = this._stringResultSet.GetOrdinal("Text");
			this._string_Offset = this._stringResultSet.GetOrdinal("Offset");
		}

		private AssemblyIndexCache _cache;
		private string _connectionString;

		private SqlCeConnection _memberConnection;
		private SqlCeCommand _memberCommand;
		private SqlCeResultSet _memberResultSet;

		private int _member_RowID;
		private int _member_Token;
		private int _member_ModuleID;
		private int _member_TargetModuleID;
		private int _member_TargetToken;
		private int _member_MemberType;
		private int _member_Usage;
		private int _member_Offset;

		private SqlCeConnection _stringConnection;
		private SqlCeCommand _stringCommand;
		private SqlCeResultSet _stringResultSet;

		private int _string_RowID;
		private int _string_Token;
		private int _string_ModuleID;
		private int _string_Text;
		private int _string_Offset;

		private Dictionary<string, int> _moduleTable;

		private void OpenResultSet(
			string tableName,
			out SqlCeConnection connection,
			out SqlCeCommand command,
			out SqlCeResultSet resultSet
			)
		{
			connection = new SqlCeConnection(this._connectionString);
			connection.Open();

			command = connection.CreateCommand();
			command.CommandText = tableName;
			command.CommandType = System.Data.CommandType.TableDirect;
			resultSet = command.ExecuteResultSet(ResultSetOptions.Updatable);
		}

		public void Dispose()
		{
			this._stringResultSet.Dispose();
			this._stringCommand.Dispose();
			this._stringConnection.Dispose();

			this._memberResultSet.Dispose();
			this._memberCommand.Dispose();
			this._memberCommand.Dispose();
		}

		private int _nextMemberDependencyID;
		private int _nextStringDependencyID;

		private int GetModuleID(Module module)
		{
			int moduleID;
			string moduleFileName = module.FullyQualifiedName;
			if (!this._moduleTable.TryGetValue(moduleFileName, out moduleID))
			{
				moduleID = this._cache.GetModuleID(module);
				this._moduleTable.Add(moduleFileName, moduleID);
			}

			return moduleID;
		}

		private static void GetMemberTypeAndUsage(
			MsilMemberDependency dependency,
			out MemberTypes memberType,
			out int usage
			)
		{
			MsilFieldDependency field = dependency as MsilFieldDependency;
			MsilMethodDependency method = dependency as MsilMethodDependency;
			MsilTypeDependency type = dependency as MsilTypeDependency;

			memberType = (MemberTypes)0;
			usage = 0;
			if (field != null)
			{
				memberType = MemberTypes.Field;
				usage = (int)field.Usage;
			}
			else if (method != null)
			{
				memberType = MemberTypes.Method;
				usage = (int)method.Usage;
			}
			else if (type != null)
			{
				memberType = MemberTypes.TypeInfo;
				usage = (int)type.Usage;
			}
		}

		public void AddMemberDependency(
			ModuleMemberReference dependent,
			MsilMemberDependency dependency
			)
		{
			if (ModuleMemberReference.IsEmpty(dependent)) throw new ArgumentNullException("dependent");
			if (dependency == null) throw new ArgumentNullException("dependency");

			int moduleID = this.GetModuleID(dependent.Module);
			int targetModuleID = this.GetModuleID(dependency.TargetModule);

			MemberTypes memberType;
			int usage;
			GetMemberTypeAndUsage(dependency,
				out memberType,
				out usage
				);

			SqlCeUpdatableRecord record = this._memberResultSet.CreateRecord();
			record.SetInt32(this._member_RowID, this._nextMemberDependencyID++);
			record.SetInt32(this._member_Token, dependent.MetadataToken);
			record.SetInt32(this._member_ModuleID, moduleID);
			record.SetInt32(this._member_TargetModuleID, targetModuleID);
			record.SetInt32(this._member_TargetToken, dependency.MetadataToken);
			record.SetInt32(this._member_MemberType, (int)memberType);
			record.SetInt32(this._member_Usage, usage);
			record.SetInt16(this._member_Offset, (short)dependency.Offset);
			this._memberResultSet.Insert(record);
			//this._memberResultSet.Update();
		}
		public void AddStringDependency(
			ModuleMemberReference dependent,
			MsilStringDependency dependency
			)
		{
			if (ModuleMemberReference.IsEmpty(dependent)) throw new ArgumentNullException("dependent");
			if (dependency == null) throw new ArgumentNullException("dependency");

			int moduleID = this.GetModuleID(dependent.Module);
			int targetModuleID = this.GetModuleID(dependency.TargetModule);

			SqlCeUpdatableRecord record = this._stringResultSet.CreateRecord();
			record.SetInt32(this._string_RowID, this._nextStringDependencyID++);
			record.SetInt32(this._string_Token, dependent.MetadataToken);
			record.SetInt32(this._string_ModuleID, moduleID);
			record.SetString(this._string_Text, dependency.GetString());
			record.SetInt16(this._string_Offset, (short)dependency.Offset);
			this._stringResultSet.Insert(record);
			//this._memberResultSet.Update();
		}
	}
}
